package cn.it.mvc.framework.servlet;

import cn.it.mvc.framework.anno.MyAutowired;
import cn.it.mvc.framework.anno.MyController;
import cn.it.mvc.framework.anno.MyRequestMapping;
import cn.it.mvc.framework.anno.MyService;
import cn.it.mvc.framework.pojo.Handler;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Pattern;

/**
 * 1、配置springmvc配置文件
 * 2、在web.xml中配置前端控制器（加载spingmvc配置文件）、请求映射路径
 * 3、初始化前端控制器
 *      （1）加载配置文件
 *      （2）包扫描，保存所有.class文件的全限定类名
 *      （3）bean实例化
 *      （4）依赖注入
 *      （5）初始化handler映射器
 * 4、处理请求，绑定参数
 *
 *
 */
public class MyDispatcherServlet extends HttpServlet {

    /**
     * 存放配置文件读取到的信息
     */
    private Properties properties = new Properties();

    /**
     * 全限定类名集合
     */
    private List<String> classNameList = new ArrayList<>();

    /**
     * ioc容器
     */
    private Map<String, Object> map = new HashMap<>();

    /**
     * 映射器
     */
    private List<Handler> handlerMapping = new ArrayList<>();


    /**
     * get请求
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    /**
     * post请求
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 获得请求对应的handler
        Handler handler = getHandler(req);
        Method method = handler.getMethod();
        Object controller = handler.getController();
        // 参数绑定
        Class<?>[] parameterTypes = method.getParameterTypes();
        Object[] args = new Object[parameterTypes.length];
        // 参数和参数值
        Map<String, String[]> parameterMap = req.getParameterMap();
        Map<String, Integer> paramIndexMapping = handler.getParamIndexMapping();
        for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
            String paramValue = StringUtils.join(param.getValue(),",");
            if (paramIndexMapping.containsKey(param.getKey())){
                Integer index = paramIndexMapping.get(param.getKey());
                args[index] = paramValue;
            }
        }

        Integer reqIndex = paramIndexMapping.get(HttpServletRequest.class.getSimpleName());
        args[reqIndex] = req;

        Integer respIndex = paramIndexMapping.get(HttpServletResponse.class.getSimpleName());
        args[respIndex] = resp;

        try {
            method.invoke(controller,args);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }

    /**
     * 从映射器获取对应的Handler
     *
     * @param req
     * @return
     */
    private Handler getHandler(HttpServletRequest req) {
        if (handlerMapping==null){
            return null;
        }

        String uri = req.getRequestURI();
        for (Handler handler : handlerMapping) {
            if (handler.getPattern().matcher(uri).matches()){
                return handler;
            }
        }

        return null;
    }

    /**
     * 初始化
     *
     * @param config
     * @throws ServletException
     */
    public void init(ServletConfig config) throws ServletException {

        // 加载配置文件
        doLoadConfig(config);

        // 包扫描
        doScan(properties.getProperty("scanPackbag"));

        // bean实例化
        doBeanInstance();

        // 依赖注入
        doAutowired();

        // 构建映射关系
        initHandlerMapping();

    }


    /**
     * 加载配置文件
     *
     * @param config
     */
    private void doLoadConfig(ServletConfig config) {
        // 获取配置文件路径
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        // 保存配置文件信息
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 包扫描
     *
     * @param scanPackbag
     */
    private void doScan(String scanPackbag) {
        // 获取包绝对路径
        String scanPackbagPath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackbag.replaceAll("\\.", "/");
        // 获取包下所有类全限定类名
        File file = new File(scanPackbagPath);
        File[] files = file.listFiles();
        if (files != null) {
            for (File sonFile : files) {
                if (sonFile.isDirectory()) {
                    // 文件夹递归继续查找
                    doScan(scanPackbag + "." + sonFile.getName());
                } else {
                    // .class文件,添加该类全限定类名
                    String className = scanPackbag + "." + sonFile.getName().replaceAll(".class", "");
                    classNameList.add(className);
                }
            }
        }
    }

    /**
     * bean实例化
     */
    private void doBeanInstance() {
        try {
            if (classNameList != null && classNameList.size() > 0) {
                for (String className : classNameList) {
                    Class<?> aClass = Class.forName(className);
                    // Controller层
                    if (aClass.isAnnotationPresent(MyController.class)) {
                        // id默认为类名首字母小写
                        String id = aClass.getSimpleName();
                        id = lowerFirst(id);
                        map.put(id, aClass.newInstance());
                    } else if (aClass.isAnnotationPresent(MyService.class)) {
                        // Service层
                        MyService annotation = aClass.getAnnotation(MyService.class);
                        String value = annotation.value();
                        // 注解value为空,默认类名首字母小写
                        if (Objects.equals(value, "")) {
                            String id = aClass.getSimpleName();
                            id = lowerFirst(id);
                            map.put(id, aClass.newInstance());
                        } else {
                            // value不为空
                            map.put(value, aClass.newInstance());
                        }

                        // 如果实现了接口,将接口全限定类名和对象放到容器中，方便依赖注入
                        Class<?>[] interfaces = aClass.getInterfaces();
                        if (interfaces.length > 0) {
                            for (Class<?> anInterface : interfaces) {
                                map.put(anInterface.getName(), aClass.newInstance());
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 字符串首字母小写
     *
     * @param id
     * @return
     */
    private String lowerFirst(String id) {
        char[] chars = id.toCharArray();
        if ('A' < chars[0] && chars[0] < 'Z') {
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }

    /**
     * 依赖注入
     */
    private void doAutowired() {
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            // 获取类中属性
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                // 是否需要依赖注入
                if (field.isAnnotationPresent(MyAutowired.class)) {
                    MyAutowired annotation = field.getAnnotation(MyAutowired.class);
                    String value = annotation.value();
                    // 有value值
                    String name = value;
                    // 无value值
                    if (Objects.equals(value, "")) {
                        name = field.getType().getName();
                    }

                    field.setAccessible(true);

                    try {
                        field.set(entry.getValue(), map.get(name));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * 初始化HandlerMapping
     */
    private void initHandlerMapping() {

        for (Map.Entry<String, Object> entry : map.entrySet()) {
            Class<?> aClass = entry.getValue().getClass();
            if (aClass.isAnnotationPresent(MyRequestMapping.class)) {
                MyRequestMapping classAnno = aClass.getAnnotation(MyRequestMapping.class);
                String classAnnoValue = classAnno.value();
                Method[] methods = aClass.getMethods();
                for (Method method : methods) {
                    if (method.isAnnotationPresent(MyRequestMapping.class)) {
                        MyRequestMapping methodAnno = method.getAnnotation(MyRequestMapping.class);
                        String methodAnnoValue = methodAnno.value();
                        Handler handler = new Handler(entry.getValue(), method, Pattern.compile(classAnnoValue + methodAnnoValue));

                        // 参数顺序
                        Parameter[] parameters = method.getParameters();
                        for (int i = 0; i < parameters.length; i++) {
                            Parameter parameter = parameters[i];
                            if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
                                handler.getParamIndexMapping().put(parameter.getType().getSimpleName(), i);
                            } else {
                                handler.getParamIndexMapping().put(parameter.getName(), i);
                            }
                        }
                        handlerMapping.add(handler);
                    }
                }
            }

        }
    }


}
